package com.boot.security.config;

import com.boot.security.config.handler.AccessExceptionHandler;
import com.boot.security.config.handler.AuthenticationExceptionHandler;
import com.boot.security.config.properties.JWTProperties;
import com.boot.security.config.properties.WebIgnoreProperties;
import com.boot.security.service.authentication.*;
import com.boot.security.service.authorization.AuthorizationFilter;
import com.boot.security.service.authorization.TokenAuthorizationManager;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.validation.Validator;

/**
 * @author 霜寒 <1621856595@qq.com>
 * @description Web安全配置
 * @date 2020/2/12 19:26
 **/
@EnableConfigurationProperties(value = {JWTProperties.class, WebIgnoreProperties.class})
@Import(value = {
        AuthenticationExceptionHandler.class, AccessExceptionHandler.class
})
@AutoConfigureAfter(SqlInitializationAutoConfiguration.class)
@MapperScan(value = "com.boot.security.mapper")
@ComponentScan(value = {"com.boot.security.controller", "com.boot.security.service"})
@EnableWebSecurity
public class SecurityWebAutoConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    WebIgnoreProperties ignoreProps;
    @Autowired
    TokenService tokenService;
    @Autowired
    TokenAuthorizationManager authorizationManager;
    @Autowired
    AuthenticationEntryPoint authenticationEntryPoint;
    @Autowired
    AccessExceptionHandler accessExceptionHandler;
    @Autowired
    FilterInvocationSecurityMetadataSource source;
    @Autowired
    AccountAuthenticationProvider accountAuthenticationProvider;
    @Autowired
    ObjectMapper om;
    @Autowired
    Validator validator;

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(accountAuthenticationProvider);
    }

    /**
     * @description org.springframework.security.config.annotation.web.builders.FilterOrderRegistration 过滤器责任链
     * @date 2023/2/4 22:40
     **/
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        if (ignoreProps.getAll()) {
            disableAllFilter(http);
        } else {
            AntPathRequestMatcher login = new AntPathRequestMatcher("/account/login", "POST");
            AntPathRequestMatcher logout = new AntPathRequestMatcher("/account/logout", "DELETE");

            // logout 需要带 token，因此 tokenFilter 只放行 login
            TokenFilter tokenFilter = new TokenFilter(login, tokenService);

            LogoutFilter logoutFilter = new LogoutFilter(logout,
                    tokenService,
                    new LogoutFilter.LogoutSuccessHandlerImpl(),
                    new LogoutFilter.LogoutFailureHandlerImpl());
            LoginFilter loginFilter = new LoginFilter(login,
                    authenticationManager(),
                    new LoginFilter.LoginSuccessHandler(tokenService),
                    new LoginFilter.LoginFailureHandler(),
                    om,
                    validator);
            AuthorizationFilter authorizationFilter = new AuthorizationFilter(source, authorizationManager);
            http
                    .addFilterAfter(tokenFilter, ExceptionTranslationFilter.class)
                    .addFilterAfter(logoutFilter, TokenFilter.class)
                    .addFilterAfter(loginFilter, LogoutFilter.class)
                    .addFilterAfter(authorizationFilter, LoginFilter.class)
                    .formLogin().disable()
                    .logout().disable()
                    .csrf().disable()
                    .requestCache().disable()
                    .httpBasic().disable()
                    .rememberMe().disable()
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .anonymous().disable()
                    .authorizeRequests()
                    .anyRequest().authenticated()
                    .accessDecisionManager(authorizationManager)
                    .and()
                    .exceptionHandling()
                    .authenticationEntryPoint(authenticationEntryPoint)
                    .accessDeniedHandler(accessExceptionHandler);
            http.headers().frameOptions().sameOrigin();
        }
    }

    private void disableAllFilter(HttpSecurity http) throws Exception {
        http
                .formLogin().disable()
                .logout().disable()
                .csrf().disable()
                .requestCache().disable()
                .httpBasic().disable()
                .rememberMe().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .anonymous().disable()
                .authorizeRequests()
                .anyRequest().permitAll();
    }

    @Override
    public void configure(WebSecurity web) {
        if (ignoreProps.getAll()) {
            web.ignoring().antMatchers("/**");
        } else {
            WebSecurity security = web.ignoring().and();
            security.ignoring()
                    .antMatchers("/error",
                            "/webjars/**",
                            "/v2/api-docs",
                            "/v2/api-docs/**",
                            "/v3/api-docs",
                            "/v3/api-docs/**",
                            "/swagger-resources",
                            "/swagger-resources/**",
                            "/swagger-ui/**",
                            "/doc.html",
                            "/configuration/ui",
                            "/configuration/security",
                            "/application/**",
                            "/ws/**",
                            "/test",
                            "/test/**",
                            "/druid/**"
                    )
                    .antMatchers(HttpMethod.GET,
                            "/favicon.ico",
                            "/static/**",
                            "/openRoute/**"
                    ).antMatchers(HttpMethod.POST, "/account/register");
            // 按照 URL 忽略
            ignoreProps.getPattern().forEach(url -> security.ignoring().antMatchers(url));
            // 忽略 GET
            ignoreProps.getGet().forEach(url -> security.ignoring().antMatchers(HttpMethod.GET, url));
            // 忽略 POST
            ignoreProps.getPost().forEach(url -> security.ignoring().antMatchers(HttpMethod.POST, url));
            // 忽略 PUT
            ignoreProps.getPut().forEach(url -> security.ignoring().antMatchers(HttpMethod.PUT, url).antMatchers(HttpMethod.OPTIONS, url));
            // 忽略 PATCH
            ignoreProps.getPatch().forEach(url -> security.ignoring().antMatchers(HttpMethod.PATCH, url).antMatchers(HttpMethod.OPTIONS, url));
            // 忽略 DELETE
            ignoreProps.getDelete().forEach(url -> security.ignoring().antMatchers(HttpMethod.DELETE, url).antMatchers(HttpMethod.OPTIONS, url));
            // OPTIONS
            ignoreProps.getOptions().forEach(url -> security.ignoring().antMatchers(HttpMethod.OPTIONS, url));

        }
    }

}
